#include "xMappingNode.h"
#include "xNodeType.h"
#include "xNodeType_VM.h"
#include "vmLibrary.h"
#include "dummyVM.h"
#include "WOP_Ref_Entry.h"
#include "WOPM.h"
#include "MappingEngine.h"
#include "xNodeType_VM_Factory.h"
#include "Command_Agent.h"
#include "xInMappingPort.h"
#include "xParameterPack.h"
#include "DIVA_VM.h"
#include "xOutMappingPort.h"

#include <sstream>

void xNodeType_VM::setupPorts() {

	assert(_parent != NULL);
	assert(_vd != NULL);


	for( auto it = this->_vd->inputParameters.begin(); it < this->_vd->inputParameters.end(); ++it ) {
		xInMappingPort *p = new xInMappingPort( _parent, it->name,
														 it->type,
														 it->klass,
														 it->defaultValue,
														 it->category) ;
		_parent->_inports.push_back(p) ;
	}
	for( auto it = this->_vd->outputParameters.begin(); it < this->_vd->outputParameters.end(); ++it ) {
		xOutMappingPort *op = new xOutMappingPort(_parent,
												it->name,
												it->type,
												it->klass,
												it->defaultValue,
												it->category) ;
		_parent->_outports.push_back(op);
	}

}

xNodeType * xNodeType_VM::toNodeTypeByLink(xInMappingPort *inp) {
			xLink *lnk = inp->getLink(); 
			assert(lnk != NULL) ;
			xOutMappingPort *outp = lnk->left ;
			xNodeType *np = outp->getParent()->getNodeTypePtr();
			return np ;
}
dummyVM* xPrepareDummyVM(WOP_Ref_Entry *ref) {
	dummyVM* dvm = NULL ;
	WOP_Entry* en = WOPM::getInstance()->check_existing_objects(ref->getVal());
	// Check if the ref's value points to an existing wop_entry
	// if yes, en != NULL,  if no, en == true ;
	
	if (en != NULL){
		// the reference points to an object which is already in WOPM
		// find its VM and set the dummyVM to it
		DIVA_VM *tvm = MappingEngine::getInstance()->get_vm_by_entry(en);
		WOP_Entry* en_t  = Command_Agent::getInstance()->unfoldRefPure(ref);
		// the name of the wop entry should be changed because the long name to access the variable 
		// can be different. Here we want to keep VM and WOPM consistent.
		// assert(en == en_t);

		// NOTE en may retrived by VD's inline unfolding, but not yet have a VM pair
		// so tvm here could be NULL, this is fine.
		dvm = new dummyVM(ref,tvm);
		MappingEngine::getInstance()->add_dummy_wop_link(ref,dvm);
	}
	else {
		dvm = new dummyVM(ref,NULL);
		MappingEngine::getInstance()->add_dummy_wop_link(ref,dvm);
	}
	assert(dvm != NULL);
	return dvm ;
}

// this method is called by 
void xNodeType_VM::createMappedObject() {
		xParameter xp ;
		dummyVM * dvm = NULL ;
		xParameterPack xpp ;
		xMappingNode *node = this->getParent();

		vmdescriptor *vd = vmLibrary::instance().get_vmdescriptor(_id) ;
		xpp.no = 0 ;
		for (int i = 0 ; i < node->getInportSize(); i++) {
			// get the nodetype pointer that connects to this inport (unique)
			xInMappingPort *inp = node->getInPort(i);
			if (inp-> getClass() == CLASS_HIDDEN) {
				// this is case of reference VM where user connect a wop to an wop port
				// but reference VM asks for a VM. So, a hidden vm port follows the wop port

				assert(vd->vm_type == "ref"); 
				assert(xp.type == "wop"); // xp is the previous parameter, should be linked with a wop
				WOP_Entry* wop = xp.val_wop ; 
				assert(wop != NULL);
				WOP_Ref_Entry * rwop = dynamic_cast<WOP_Ref_Entry *>(wop);
				assert(rwop != NULL);	 // if dynamic_cast is failure, rwop would be NULL
				dvm  = xPrepareDummyVM(rwop);
				xp.val_vm = dvm ;
			} else {
				xp = inp->toXParameter();
			}
			xpp.no ++ ;
			xpp.paras.push_back(xp); 
			if (inp->getLink() != NULL) xpp.para_onoff.push_back(true);
			else xpp.para_onoff.push_back(false);
			
		}

		_vm = xNodeType_VM_Factory::createVM(_id, xpp);
		
		MappingEngine::getInstance()->addNewVM(_vm); // this statement add the new VM into a data structure
		// called _newcreated.
		// MappingEngine::doLayoutForNewVM()  will read this vector
		// and apply layout such as regular_pointer_new_layoutVM to the new VM
		// if you do not want to new layout to apply, disable it by DIVA_VM::disable_newlayout();
		if (vd->vm_type == "ref") {
				assert(dvm != NULL);
				dvm->setParentVM(_vm); 
		}
	
}
bool xNodeType_VM::update() {	_vm->VMValueUpdated();   return true ; }
xParameter xNodeType_VM::toXParameter(string toType, xOutMappingPort * linkedop) {
	xParameter xp ;
	
	int i = linkedop->getIndexOfParent();
	assert(i>=0) ;
		//xp.original = _wop->getVal();
	switch(i) {
		case 0: 
					xp.type = T_VM ;
					assert(_vm != NULL);
					xp.val_vm = _vm ;
					return xp ;
		case 1: 
					xp.type = T_STRING ;
					xp.val_string = _vm->get_vm_name(); // varname
					return xp ;
		case 2: // varname_position
					xp.type = T_INT ;
					xp.val_int = _vm->get_vm_name_position();
					return xp ;
		case DIVA_VM::RC+1: 	// rx 
					if (toType == TP_TOSTRING) {	
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_rx_str();
					} else {
						xp.type = TP_INT ;
						xp.val_float_ptr = _vm->get_vm_rx();
					}
					return xp ;
		case DIVA_VM::RC+2 : // ry ;
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_ry_str();
					} else {
						xp.type = TP_INT ;
						xp.val_float_ptr = _vm->get_vm_ry();
					}
					return xp ;
		case DIVA_VM::RC+3 : 
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_rz_str();
					} else {
						xp.type = TP_INT ;
						xp.val_float_ptr = _vm->get_vm_rz();
					}
					return xp ;// rz 
		case DIVA_VM::SIZE+1 : // sizex
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_sizex_str();
					} else {
						xp.type = TP_INT ;
						xp.val_float_ptr = _vm->get_vm_sizex();
					}
					return xp ;
		case DIVA_VM::SIZE+2 : // sizey 
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_sizey_str();
					} else {
						xp.type = TP_INT ;
						xp.val_float_ptr = _vm->get_vm_sizey();
					}
					return xp ;
		case DIVA_VM::SIZE+3:  // sizez
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_sizez_str();
					} else {
						xp.type = TP_INT ;
						xp.val_float_ptr = _vm->get_vm_sizez();
					}
					return xp ; 
		case DIVA_VM::SCALE+1: // scalesizex 
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_sizexscale_str();
					} else {	
						xp.type = TP_DOUBLE ;
						xp.val_double_ptr = _vm->get_vm_sizexscale();
					}
					return xp ;
		case DIVA_VM::SCALE+2: // scalesizey 
					
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_sizeyscale_str();
					} else {
						xp.type = TP_DOUBLE ;
						xp.val_double_ptr = _vm->get_vm_sizeyscale();
					}
					return xp ;
		case DIVA_VM::SCALE+3: // scalesizez
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_sizezscale_str();
					} else {
						xp.type = TP_DOUBLE ;
						xp.val_double_ptr = _vm->get_vm_sizezscale();
					}
					return xp ;
		case DIVA_VM::RGB+1 : // r
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_r_str();
					} else {
						xp.type = TP_DOUBLE ;
						xp.val_double_ptr = _vm->get_vm_r();
					}
					return xp ;
		case DIVA_VM::RGB+2 : // g
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_g_str();
					} else {
						xp.type = TP_DOUBLE ;
						xp.val_double_ptr = _vm->get_vm_g();
					}
					return xp ;
		case DIVA_VM::RGB+3:  // b
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_b_str();
					} else {
						xp.type = TP_DOUBLE ;
						xp.val_double_ptr = _vm->get_vm_b();
					}
					return xp ;
		case DIVA_VM::TRANS+1:// transparency
					assert( !"transparency is not supported yet" ); // under construction
		case DIVA_VM::ROTATE+1 : // yaw 
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_yaw_str();
					} else {
					xp.type = TP_INT ;
					xp.val_int_ptr = _vm->get_vm_yaw();
					}
					return xp ; 
		case DIVA_VM::ROTATE+2 : // pitch 
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_pitch_str();
					} else {
					xp.type = TP_INT ;
					xp.val_int_ptr = _vm->get_vm_pitch();
					}
					return xp ; 
		case DIVA_VM::ROTATE+3 : // roll
					if (toType == TP_TOSTRING) {
						xp.type = TP_TOSTRING ;
						xp.val_string_ptr = _vm->get_vm_roll_str();
					} else {
					xp.type = TP_INT ;
					xp.val_int_ptr = _vm->get_vm_roll();
					}
					return xp ; 
		case DIVA_VM::QUAT+1:	// quaternion
					assert(false); // under construction
		case DIVA_VM::CLICKMSG+1 : // clickmsg
					xp.type = TP_STRING ;
					xp.val_string_ptr = _vm->get_vm_clickmsg();
					return xp ; 
		case DIVA_VM::CLICKMSG+2: // clickmsg_pos
					assert(toType != TP_TOSTRING);
					xp.type = T_INT ;
					xp.val_int = _vm->get_vm_clickmsg_position();
					return xp ;
		default:
			assert(false); // should not reach here
			return xp ;
	}

}
/** this method is called when a mapping tree is saved to a MDS file.
    In a MDS file, a mapping node is described by 

	node serialno type para1 para2 

*/  
string xNodeType_VM::toMDString(MDSType type ) {
	ostringstream MDSstr ;
	xParameter xp ;
	int inp_no = _vd->inputParameters.size() ; 

	if ( type == xNodeType::NormalMDSFormat){
		MDSstr << "node "<<  _parent->getSerialNo() << " vm " << _idname << " " << inp_no << endl ; 
	} else if ( type == xNodeType::ParameterizedMDSFormat ){
		MDSstr << "ADD_NODE " << "node "<< "SN" << _parent->getSerialNo() << " vm " << _idname << " " << inp_no << endl ; 
	} else {
		assert(false);
	}
	
	MDSstr << "  defaultstring<" ;

	xMappingNode *node = this->getParent();

	for (int i = 0 ; i < node->getInportSize(); i++) {
			// get the nodetype pointer that connects to this inport (unique)
			xInMappingPort *inp = node->getInPort(i);
			MDSstr << " " << inp->getDefaultValue();
	}
	MDSstr << " >" << endl ;
	return MDSstr.str();
}

int xNodeType_VM::parseMDString(vector<string>& tokens, int start_index) {
	int i = start_index ;
	assert(tokens[i] == "node");
	string serialno = tokens[i+1] ;
	assert(tokens[i+2] == "vm");
	string vmname = tokens[i+3] ; // nodetypes are "wop" "vm" "vmcollector"
	string inport_no = tokens[i+4] ;
	
	istringstream buf2(inport_no);
	int inp_no ;
	buf2 >> inp_no ;

	assert(tokens[i+5] == "defaultstring<") ;
	vmdescriptor* vd = this->get_vmdescriptor();
	assert(vd->inputParameters.size() == inp_no) ;

	i+=6 ;
	for (int k=0; k < inp_no; k++) {
		xMappingNode* p = this->getParent();
		xInMappingPort* in = p->getInPort(k);
		in->setDefaultValue(tokens[i+k]);
	}
	assert(tokens[i+inp_no] == ">"); 
	return inp_no+7 ; // 5 tokens begins with "node", defaultstring< is one token
	                  // and > is the last tokens ;
}
string xNodeType_VM::valid_rhs_pair[][2] = { 
// in a VM node, the outport can only have type (int*, double*, tostring*	
"int*", "wop", 
"double*", "wop",
"dontcare", "tostring*"
};

bool xNodeType_VM::verify_rhs_link(xOutMappingPort *left, xInMappingPort *right, xNodeType* rightnode) {
	if (left->getType() == right->getType()) return true ;
	return check_valid_pair(valid_rhs_pair, sizeof(valid_rhs_pair)/(sizeof(string)*2),
							left->getType(), right->getType());
}
bool xNodeType_VM::verify_lhs_link(xOutMappingPort *left, xNodeType* leftnode, xInMappingPort *right) {
	
	return true ; // when a link is on the left hand side of this node
	              // we have the leftnode to check for the validity
}